"""
This is my attempt at a Pythonic version of gshhs.c  (http://www.soest.hawaii.edu/pwessel)

It generates the same answer as the c. (I takes a bit longer but hey..)

The latest downloads seem to be here...

http://www.ngdc.noaa.gov/mgg/shorelines/data/gshhg/latest/

Tested with http://www.ngdc.noaa.gov/mgg/shorelines/data/gshhg/latest/gshhg-bin-2.2.2.zip
"""

import sys
import os.path
from struct import calcsize, unpack

VERSION = 9 # target gshhs version (no change since 7?)
MICRO_DEG2DEG = 1.0e-6

class Point(object):
    """
    """
    max_east = 270

    def __init__(self, header, binary):
        """
        unpack longitude and latitude.
        """
        self.lon = unpack('>i', binary[ 0*4 : 1*4])[0] * MICRO_DEG2DEG
        if header.greenwich and self.lon>self.max_east or header.west>180:
            self.lon -= 360
        self.lat = unpack('>i', binary[ 1*4 : 2*4])[0] * MICRO_DEG2DEG

    def __str__(self):
        """
        """
        return '%11.6f%11.6f' % (self.lon, self.lat)

class Points(object):
    """
    """

    def __init__(self, parser, header, binary):
        """
        given a parser, a header and a lump of binary data construct points.
        """
        for b in range(0, len(binary), 8):
            parser.point(Point(header, binary[b:b+8]))
        Point.max_east = 180

class Header(object):
    """
    """
    LENGTH = 11*4 # eleven four byte ints

    def __init__(self, binary):
        """
        given a lump of binary data construct a suitable header object.
        """
        self.pid       = unpack('>i', binary[ 0*4 :  1*4])[0]
        self.n         = unpack('>i', binary[ 1*4 :  2*4])[0]
        flag           = unpack('>i', binary[ 2*4 :  3*4])[0]
        self.level     =  flag        & 255
        self.version   = (flag >>  8) & 255
        assert self.version==VERSION, self.version
        self.greenwich = (flag >> 16) & 1
        source         = (flag >> 24) & 1
        self.river     = (flag >> 25) & 1
        source         = 'W' if source==1 else 'C'
        self.source    = source.lower() if self.river else source
        self.west      = unpack('>i', binary[ 3*4 :  4*4])[0] * MICRO_DEG2DEG
        self.east      = unpack('>i', binary[ 4*4 :  5*4])[0] * MICRO_DEG2DEG
        self.south     = unpack('>i', binary[ 5*4 :  6*4])[0] * MICRO_DEG2DEG
        self.north     = unpack('>i', binary[ 6*4 :  7*4])[0] * MICRO_DEG2DEG
        self.area      = unpack('>i', binary[ 7*4 :  8*4])[0] * 0.1 # km^2
        self.c         = 'L' if self.area == 0 else 'P'
        self.f_area    = unpack('>i', binary[ 8*4 :  9*4])[0] * 0.1 # km^2
        c =self.container = unpack('>i', binary[ 9*4 : 10*4])[0]
        self.container_str = '-' if c == -1 else '%6d' % c
        a = self.ancestor  = unpack('>i', binary[10*4 : 11*4])[0]
        self.ancestor_str = '-' if a == -1 else '%6d' % a

    def __str__(self):
        """
        L(ine) or P(olygon)
        """
        if self.c=='L':
            return '%c %6d%8d%2d%2c%10.5f%10.5f%10.5f%10.5f' % (self.c, self.pid, self.n, self.level, self.source,
                                                                self.west, self.east, self.south, self.north)
        return '%c %6d%8d%2d%2c%13.3f%13.3f%10.5f%10.5f%10.5f%10.5f %s %s' % (self.c, self.pid, self.n, self.level, self.source, self.area, self.f_area,
                                                                              self.west, self.east, self.south, self.north, self.container_str, self.ancestor_str)

class Parser(object):
    """
    """

    def __init__(self, path):
        """
        path is a binary gshhs.
        """
        with open(path, 'rb') as handle:
            header = handle.read(Header.LENGTH)
            while header:
                h = Header(header)
                self.header(h)
                h.points = Points(self, h, handle.read(h.n*4*2))
                header = handle.read(Header.LENGTH)
        self.done()

    def header(self, h):
        """
        parser callback (derive your own).
        """
        print h

    def point(self, p):
        """
        parser callback (derive your own).
        """
        print p

    def done(self):
        """
        parser callback (derive your own).
        """

if __name__=='__main__':
    path = sys.argv[1]
    assert os.path.exists(path), path
    Parser(path)
